//
//  NoteReviewService.swift
//  hello
//
//  Created by 赵康 on 2025/4/2.
//

import Fluent
import Foundation
import Vapor

struct NoteReviewService {
  // MARK: - 主要计算方法

  /// 计算下一次复习日期和更新笔记状态
  static func calculateNextReview(
    review: NoteReview,
    difficulty: NoteReview.Difficulty,
    settings: UserReviewSettings
  ) -> (
    nextDate: Date?, easeFactor: Double, interval: Double, learningStage: NoteReview.LearningStage,
    lapseCount: Int
  ) {
    let now = Date()
    _ = Calendar.mondayFirst

    // 如果设置为不再复习，直接返回
    if difficulty == .ignored {
      return (nil, review.easeFactor, 0, .suspended, 0)
    }

    // 根据当前学习阶段来处理...
    let result:
      (
        nextDate: Date?, easeFactor: Double, interval: Double,
        learningStage: NoteReview.LearningStage, lapseCount: Int
      )

    switch review.learningStage {
    case .new, .learning:
      result = handleLearningStage(review: review, difficulty: difficulty, settings: settings)
    case .reviewing:
      result = handleReviewingStage(review: review, difficulty: difficulty, settings: settings)
    case .relearning:
      result = handleRelearningStage(review: review, difficulty: difficulty, settings: settings)
    case .suspended:
      result = (now, 2.5, 0, .new, 0)
    }

    return result
  }

  // MARK: - 学习阶段处理

  /// 处理学习阶段的卡片
  private static func handleLearningStage(
    review: NoteReview,
    difficulty: NoteReview.Difficulty,
    settings: UserReviewSettings
  ) -> (
    nextDate: Date?, easeFactor: Double, interval: Double, learningStage: NoteReview.LearningStage,
    lapseCount: Int
  ) {

    let now = Date()
    let calendar = Calendar.mondayFirst

    // 获取学习步骤
    let steps = settings.learningSteps

    // 如果没有配置学习步骤，使用默认值
    guard !steps.isEmpty else {
      return (
        calendar.date(byAdding: .minute, value: 10, to: now), review.easeFactor, 0, .learning, 0
      )
    }

    switch difficulty {
    case .again:
      // 回到第一步
      let minutes = steps[0]
      return (
        calendar.date(byAdding: .minute, value: minutes, to: now),
        max(1.3, review.easeFactor - 0.2),
        0,
        .learning,
        0
      )

    case .hard:
      // 当前步骤不变
      let currentStep = min(review.reviewCount, steps.count - 1)
      let minutes = steps[currentStep]
      return (
        calendar.date(byAdding: .minute, value: minutes, to: now),
        max(1.3, review.easeFactor - 0.15),
        0,
        .learning,
        0
      )

    case .good:
      // 进入下一步骤
      let nextStep = min(review.reviewCount + 1, steps.count - 1)

      // 如果已经是最后一步，进入复习阶段
      if nextStep == steps.count - 1 && review.reviewCount >= steps.count - 1 {
        // 毕业到复习阶段
        return (
          calendar.date(byAdding: .day, value: 1, to: now),
          review.easeFactor,
          1.0,  // 第一个复习间隔为1天
          .reviewing,
          0
        )
      } else {
        // 继续学习
        let minutes = steps[nextStep]
        return (
          calendar.date(byAdding: .minute, value: minutes, to: now),
          review.easeFactor,
          0,
          .learning,
          0
        )
      }

    case .easy:
      // 直接跳到复习阶段
      return (
        calendar.date(byAdding: .day, value: 4, to: now),
        min(review.easeFactor + 0.15, 2.5),
        4.0,  // 简单卡片直接设为4天
        .reviewing,
        0
      )

    case .ignored:
      // 不应该走到这里，但为完整性保留
      return (nil, review.easeFactor, 0, .suspended, 0)
    }
  }

  /// 处理复习阶段的卡片
  private static func handleReviewingStage(
    review: NoteReview,
    difficulty: NoteReview.Difficulty,
    settings: UserReviewSettings
  ) -> (
    nextDate: Date?, easeFactor: Double, interval: Double, learningStage: NoteReview.LearningStage,
    lapseCount: Int
  ) {

    let now = Date()
    let calendar = Calendar.mondayFirst

    // 根据不同难度选项计算新的间隔和难度因子
    switch difficulty {
    case .again:
      // 遗忘，进入重新学习状态
      let lapseCount = review.lapseCount + 1

      // 应用设置中的遗忘步骤
      let lapseSteps = settings.lapseSteps
      let minutes = lapseSteps.isEmpty ? 10 : lapseSteps[0]

      // 计算新间隔（乘以遗忘百分比）
      let newInterval = max(1.0, review.interval * settings.lapseNewInterval)

      // 如果达到遗忘阈值，考虑暂停
      if lapseCount >= settings.leechThreshold {
        // 在实际应用中，可能会弹出提示或自动暂停
        // 这里我们仍然让它进入重新学习状态
      }

      return (
        calendar.date(byAdding: .minute, value: minutes, to: now),
        max(1.3, review.easeFactor - 0.2),
        newInterval,
        .relearning,
        lapseCount
      )

    case .hard:
      let newInterval = review.interval * 1.2 * settings.intervalModifier
      let cappedInterval = min(Double(settings.maxInterval), newInterval)

      return (
        calendar.date(byAdding: .day, value: Int(cappedInterval), to: now),
        max(1.3, review.easeFactor - 0.15),
        cappedInterval,
        .reviewing,
        review.lapseCount
      )

    case .good:
      let newInterval = review.interval * review.easeFactor * settings.intervalModifier
      let cappedInterval = min(Double(settings.maxInterval), newInterval)

      return (
        calendar.date(byAdding: .day, value: Int(cappedInterval), to: now),
        review.easeFactor,
        cappedInterval,
        .reviewing,
        review.lapseCount
      )

    case .easy:
      let newInterval = review.interval * review.easeFactor * 1.3 * settings.intervalModifier
      let cappedInterval = min(Double(settings.maxInterval), newInterval)

      return (
        calendar.date(byAdding: .day, value: Int(cappedInterval), to: now),
        min(review.easeFactor + 0.15, 2.5),
        cappedInterval,
        .reviewing,
        review.lapseCount
      )

    case .ignored:
      return (nil, review.easeFactor, 0, .suspended, 0)
    }
  }

  /// 处理重新学习阶段的卡片
  private static func handleRelearningStage(
    review: NoteReview,
    difficulty: NoteReview.Difficulty,
    settings: UserReviewSettings
  ) -> (
    nextDate: Date?, easeFactor: Double, interval: Double, learningStage: NoteReview.LearningStage,
    lapseCount: Int
  ) {

    let now = Date()
    let calendar = Calendar.mondayFirst

    // 获取遗忘后的学习步骤
    let steps = settings.lapseSteps

    // 如果没有配置学习步骤，使用默认值
    guard !steps.isEmpty else {
      return (
        calendar.date(byAdding: .minute, value: 10, to: now), review.easeFactor, review.interval,
        .relearning, review.lapseCount
      )
    }

    switch difficulty {
    case .again:
      // 回到第一步
      let minutes = steps[0]
      return (
        calendar.date(byAdding: .minute, value: minutes, to: now),
        max(1.3, review.easeFactor - 0.2),
        review.interval,
        .relearning,
        review.lapseCount
      )

    case .hard:
      // 当前步骤不变
      let currentStep = min(review.reviewCount % steps.count, steps.count - 1)
      let minutes = steps[currentStep]
      return (
        calendar.date(byAdding: .minute, value: minutes, to: now),
        max(1.3, review.easeFactor - 0.15),
        review.interval,
        .relearning,
        review.lapseCount
      )

    case .good:
      // 进入下一步骤
      let currentStep = review.reviewCount % steps.count
      let nextStep = currentStep + 1

      // 如果已经是最后一步，回到复习阶段
      if nextStep >= steps.count {
        // 返回复习阶段
        return (
          calendar.date(byAdding: .day, value: Int(review.interval), to: now),
          review.easeFactor,
          review.interval,
          .reviewing,
          review.lapseCount
        )
      } else {
        // 继续重新学习
        let minutes = steps[nextStep]
        return (
          calendar.date(byAdding: .minute, value: minutes, to: now),
          review.easeFactor,
          review.interval,
          .relearning,
          review.lapseCount
        )
      }

    case .easy:
      // 直接回到复习阶段，并增加间隔
      return (
        calendar.date(byAdding: .day, value: Int(review.interval * 1.3), to: now),
        min(review.easeFactor + 0.15, 2.5),
        review.interval * 1.3,
        .reviewing,
        review.lapseCount
      )

    case .ignored:
      return (nil, review.easeFactor, 0, .suspended, 0)
    }
  }

  // MARK: - 预测复习量

  /// 预测未来时间段内的复习量
  /// - Parameters:
  ///   - userID: 用户ID
  ///   - db: 数据库连接
  ///   - startDate: 开始预测的日期（默认为今天）
  ///   - timeRange: 预测时间范围（天数）
  /// - Returns: 按日期分组的预测复习量数据
  static func predictReviewVolume(
    userID: UUID,
    db: Database,
    startDate: Date? = nil,
    timeRange: Int
  ) async throws -> [Date: Int] {
    let calendar = Calendar.mondayFirst
    let today = calendar.startOfDay(for: Date())
    let predictionStartDate = startDate != nil ? calendar.startOfDay(for: startDate!) : today

    // 创建日期区间，指定开始日期 + 未来timeRange天
    var predictions = [Date: Int]()
    for dayOffset in 0..<timeRange {
      if let date = calendar.date(byAdding: .day, value: dayOffset, to: predictionStartDate) {
        predictions[date] = 0
      }
    }

    // 获取所有当前未完成复习的笔记
    let reviews = try await NoteReview.query(on: db)
      .filter(\.$user.$id == userID)
      .filter(\.$nextReviewDate != nil)
      .all()

    // 获取用户复习设置
    let settings =
      try await UserReviewSettings.query(on: db)
      .filter(\.$user.$id == userID)
      .first() ?? UserReviewSettings.defaultSettings(userID: userID)

    // 计算每个笔记的未来复习日期并分组
    for review in reviews {
      // 如果下一次复习日期已经在预测区间内，直接计入
      if let nextDate = review.nextReviewDate {
        let dateKey = calendar.startOfDay(for: nextDate)
        if predictions.keys.contains(dateKey) {
          predictions[dateKey, default: 0] += 1
          continue
        }
      }

      // 如果处于学习/重新学习阶段，通常是当天或近期内复习
      if review.learningStage == .learning || review.learningStage == .relearning {
        if let nextDate = review.nextReviewDate,
          calendar.isDateInToday(nextDate)
        {
          if predictions.keys.contains(predictionStartDate) {
            predictions[predictionStartDate, default: 0] += 1
          }
        }
        continue
      }

      // 对于复习阶段的笔记，模拟可能的复习路径
      if review.learningStage == .reviewing {
        // 假设用户选择"良好"难度，计算下一个复习日期
        let result = calculateNextReview(
          review: review,
          difficulty: .good,
          settings: settings
        )

        if let nextDate = result.nextDate {
          let dateKey = calendar.startOfDay(for: nextDate)
          if predictions.keys.contains(dateKey) {
            predictions[dateKey, default: 0] += 1
          }
        }
      }
    }

    return predictions
  }
}
